﻿using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using DimensionsHelper.LanguageService.Protocol;

namespace DimensionsHelper.LanguageService.Syntax;

/// <summary>
///     IBM DataCollection Script脚本源文件。
/// </summary>
[DebuggerDisplay("{FileName,nq}")]
public class SourceTextFile
{
    private readonly HashSet<SourceTextFile> _includeFiles;
    private readonly HashSet<SourceTextFile> _referencedFiles;
    private readonly Dictionary<DocumentUri, SyntaxTree> _trees;
    private string _text;

    internal SourceTextFile(SourceFileManager manager, string fullPath, string text, Encoding encoding)
    {
        Manager = manager;
        _text = text;
        LineMap = new SourceTextLineMap(_text);
        _trees = new Dictionary<DocumentUri, SyntaxTree>(DocumentUriComparer.Default);
        Directory = Path.GetDirectoryName(fullPath) ?? ".";
        FileName = Path.GetFullPath(fullPath);
        Uri = DocumentUriCache.GetCached(fullPath);
        Encoding = encoding;
        Entry = this;
        GetSourceFileType(FileName);

        _includeFiles = [];
        _referencedFiles = [];

        TimeStamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
    }


    internal SourceTextFile(SourceFileManager manager, string fullPath, string text) :
        this(manager, fullPath, text, Encoding.UTF8)
    {
    }


    /// <summary>
    ///     文件所在的源文件管理器
    /// </summary>
    public SourceFileManager Manager { get; }


    /// <summary>
    ///     文本文件的行号表
    /// </summary>
    public SourceTextLineMap LineMap { get; private set; }


    /// <summary>
    ///     源文件路径
    /// </summary>
    public string FileName { get; }

    /// <summary>
    ///     源文件URI
    /// </summary>
    public DocumentUri Uri { get; }

    /// <summary>
    ///     源文件文件夹路径
    /// </summary>
    public string Directory { get; }


    /// <summary>
    ///     当前文件的编码格式
    /// </summary>
    public Encoding Encoding { get; }


    /// <summary>
    ///     代码源文件类型
    /// </summary>
    public SourceFileType Type { get; private set; }


    /// <summary>
    ///     获取当前文件的当前入口文件
    /// </summary>
    public SourceTextFile Entry { get; private set; }


    /// <summary>
    ///     获取文件的文本字符长度
    /// </summary>
    public int Length { get; private set; }

    /// <summary>
    ///     获取文件的结束位置
    /// </summary>
    public int End => Length;

    /// <summary>
    ///     预处理后的文件内容
    /// </summary>
    public ProcessedFile? Preprocessed { get; internal set; }


    /// <summary>
    ///     如果文件是解析入口文件（没有被其他文件引用），返回<see langword="true" />。
    /// </summary>
    public bool IsEntry => _referencedFiles.Count == 0;


    /// <summary>
    ///     文件内容更新的时间戳
    /// </summary>
    public long TimeStamp { get; private set; }


    private void GetSourceFileType(string fileName)
    {
        if (fileName.EndsWith(".d.mrs", StringComparison.OrdinalIgnoreCase))
        {
            Type = SourceFileType.Declare;
        }
        else if (fileName.EndsWith(".dms", StringComparison.OrdinalIgnoreCase))
        {
            Type = SourceFileType.Dms;
        }
        else
        {
            Type = SourceFileType.Mrs;
        }
    }


    /// <summary>
    ///     获取相对路径在当前文件路径和根路径下的完整路径。
    /// </summary>
    public string GetFullPath(string relativePath)
    {
        if (relativePath.StartsWith('.') ||
            relativePath.StartsWith('\\') ||
            relativePath.StartsWith('/'))
        {
            return Path.GetFullPath(Path.Combine(Directory, relativePath));
        }

        return Path.GetFullPath(Path.Combine(Manager.Root, relativePath));
    }


    /// <summary>
    ///     获取文件的文本内容
    /// </summary>
    public string GetText()
    {
        return _text;
    }


    /// <summary>
    ///     获取指定区间的文本
    /// </summary>
    /// <param name="start">起始位置</param>
    /// <param name="length">文本宽度</param>
    public string Substring(int start, int length)
    {
        Debug.Assert(start >= 0 && start + length <= _text.Length && length >= 0);
        return length == 0 ? "" : _text.Substring(start, length);
    }


    /// <summary>
    ///     <inheritdoc cref="Substring(int, int)" />
    /// </summary>
    public string Substring(in TextSpan span)
    {
        return Substring(span.Start, span.Width);
    }


    /// <summary>
    ///     <inheritdoc cref="Substring(int,int)" />
    /// </summary>
    public string Substring(ITextRange range)
    {
        return Substring(range.Span);
    }


    /// <summary>
    ///     更新文件的文本内容
    /// </summary>
    public void UpdateContent(string content)
    {
        if (_text == content)
        {
            return;
        }

        _text = content;

        TimeStamp = DateTimeOffset.Now.ToUnixTimeMilliseconds();
        LineMap = new SourceTextLineMap(content);
        Length = content.Length;
        Preprocessed = null;
    }


    /// <summary>
    ///     获取当前所有已解析的语法树
    /// </summary>
    /// <returns>文件语法树列表</returns>
    public ImmutableArray<SyntaxTree> GetSyntaxTrees()
    {
        return [.._trees.Values];
    }


    /// <summary>
    ///     尝试使用解析入口文件名查找已有语法树，此方法不会创建新的语法树
    /// </summary>
    /// <param name="entryFileName">入口文件路径，可以是相对路径或完整路径</param>
    /// <param name="tree">已有的语法树</param>
    public bool TryGetSyntaxTreeByEntryFileName(string entryFileName, [MaybeNullWhen(false)] out SyntaxTree tree)
    {
        entryFileName = Manager.GetFullPath(entryFileName);
        return _trees.TryGetValue(DocumentUriCache.GetCached(entryFileName), out tree);
    }


    /// <summary>
    ///     更新文件的指定入口文件的语法树
    /// </summary>
    public void UpdateSyntaxTree(string entryFileName, SyntaxTree tree)
    {
        DocumentUri uri = DocumentUriCache.GetCached(entryFileName);
        if (_trees.TryAdd(uri, tree))
        {
            return;
        }

        _trees[uri].ClearDiagnostics();
        _trees[uri] = tree;
    }


    /// <summary>
    ///     设置当前文件的入口文件，如果指定文件没有包含此文件，返回<see langword="false" />。
    ///     允许将入口文件设置为自身。
    /// </summary>
    public bool SetEntryFile(SourceTextFile source)
    {
        if (source == this)
        {
            Entry = source;
            return true;
        }

        if (!source.ContainsFile(this))
        {
            return false;
        }

        Entry = source;
        return true;
    }


    /// <summary>
    ///     获取此文件的入口文件列表。
    /// </summary>
    public ImmutableArray<SourceTextFile> GetEntryFiles()
    {
        return IsEntry ? [this] : [.._referencedFiles.SelectMany(f => f.GetEntryFiles()).ToHashSet()];
    }


    /// <summary>
    ///     添加源文件引用
    /// </summary>
    /// <remarks>
    ///     如果构成递归引入，将返回<see langword="false" />
    /// </remarks>
    public bool IncludeFile(SourceTextFile sourceTextFile)
    {
        // 如果引用列表中包含给定文件，则构成递归引用，返回 false。
        if (_referencedFiles.Contains(sourceTextFile))
        {
            return false;
        }

        _includeFiles.Add(sourceTextFile);
        sourceTextFile._referencedFiles.Add(this);
        return true;
    }


    /// <summary>
    ///     移除指定的引用文件
    /// </summary>
    public void RemoveIncludeFile(SourceTextFile sourceTextFile)
    {
        _referencedFiles.Remove(sourceTextFile);
    }


    /// <summary>
    ///     获取所有引用的文件
    /// </summary>
    public ImmutableArray<SourceTextFile> GetIncludeFiles()
    {
        return [.._includeFiles];
    }


    /// <summary>
    ///     获取所有引用此文件的文件
    /// </summary>
    public ImmutableArray<SourceTextFile> GetReferencedFiles()
    {
        return [.._referencedFiles];
    }


    /// <summary>
    ///     移除指定引用此文件的文件
    /// </summary>
    public void RemoveReferenceFile(SourceTextFile sourceTextFile)
    {
        _referencedFiles.Remove(sourceTextFile);
    }


    /// <summary>
    ///     如果此文件直接包含或者子文件包含给定文件，返回<see langword="true" />
    /// </summary>
    public bool ContainsFile(SourceTextFile file)
    {
        return _includeFiles.Contains(file) || _includeFiles.Any(includeFile => includeFile.ContainsFile(file));
    }
}